package research_aggregator;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Semaphore;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

public class Cache  extends Thread {
	public Hashtable<String, DeIdentifiedHealthRecord> records;
	private SSLConnection sslDS;
	private SSLConnection sslKS;
	private Semaphore sem_cache;
	private Semaphore sem_read;
	private int count;
	
	public Cache(Semaphore sem_cache, Semaphore sem_read, String DsIp, int DsPort, String KsIp, int KsPort){
		this.sem_cache = sem_cache;
		this.sem_read = sem_read;
		records = new Hashtable<String, DeIdentifiedHealthRecord>();
		sslDS = new SSLConnection(DsIp, DsPort);
		sslKS = new SSLConnection(KsIp, KsPort);		
	}
	
	public void run() {
		try {
			sleep(86400000);		
			update();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
	
	public void load() throws InterruptedException {
		count = 0;
		sem_cache.acquire();
		try {			
			sslDS.connect();
			sslDS.writeLine(new Message().setMessage("loadAllHealthRecord").toXml());
			
			Message message = new Message(sslDS.readLine());
			while (!message.getMessage().equals("end")) {
				System.out.println(message.toXml());
				insertRecord(message);
				count++;
				message = new Message(sslDS.readLine());
			}
			sslDS.close();
			System.out.println("[" + count + "] records are loaded");
		}
		catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		sem_cache.release();
	}
	
	public void update() throws InterruptedException {
		sem_cache.acquire();
		try {
			sslDS.connect();
			sslDS.writeLine(new Message().setMessage("cacheHealthRecord").toXml());
			
			Message message = new Message(sslDS.readLine());
			while (!message.getMessage().equals("end")) {
				insertRecord(message);			
			}
			sslDS.close();
		}
		catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		sem_cache.release();
	}
	
	public void insertRecord(Message message) {
		String identifier = message.getHealthRecordAttribute("identifier");
		if (records.containsKey(identifier)) records.remove(identifier);
		String publicKey = getPublicKey(identifier);
		String publicRecord = decrypt(message.getHealthRecordAttribute("public"), publicKey);
		System.out.println("insert = " + publicRecord);
		records.put(identifier, new DeIdentifiedHealthRecord(publicRecord));
	}
	
	private String getPublicKey (String identifier) {
		try {
			sslKS.connect();
			sslKS.writeLine(new Message().setMessage("requestPublicKey")
					.setKey(null, identifier, null, null).toXml());
			String temp = sslKS.readLine();
			System.out.println("ks = read line " + temp);
			Message message = new Message(temp);
			
			String key;
			System.out.println("keystore server sent " + message.toXml());
			key = message.getKeyAttribute("public");
			sslKS.close();
			return key;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			try {sslKS.close();}
			catch (Exception e1) {;}
			return null;
		}		
	}
	
	public Hashtable<String, DeIdentifiedHealthRecord> getList() {
		return records;
	}
	
	/*
	 * http://stackoverflow.com/questions/521101/using-sha1-and-rsa-with-java-security-signature-vs-messagedigest-and-cipher
	 */
	private String bytes2String(byte[] bytes) {
		StringBuilder string = new StringBuilder();
		for (byte b: bytes) {
			String hexString = Integer.toHexString(0x00FF & b);
			string.append(hexString.length() == 1 ? "0" + hexString : hexString);
		}
		return string.toString();
	}

	/*
	 * http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
	 */
	public byte[] string2Bytes(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
					+ Character.digit(s.charAt(i+1), 16));
		}
		return data;
	}

	public String encrypt (String data, String key) {
		byte[] bKey = string2Bytes(key);
		SecretKeySpec skeySpec = new SecretKeySpec(bKey, "AES");

		Cipher cipher;
		try {
			cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

			byte[] encrpyted = cipher.doFinal(data.getBytes());

			return bytes2String(encrpyted);


		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;		
	}

	public String decrypt (String data, String key) {
		byte[] bKey = string2Bytes(key);
		SecretKeySpec skeySpec = new SecretKeySpec(bKey, "AES");

		Cipher cipher;
		try {
			cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, skeySpec);

			byte[] decrypted = cipher.doFinal(string2Bytes(data));

			return convertHexToString(bytes2String(decrypted));


		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;		
	}

	/*
	 * http://www.mkyong.com/java/how-to-convert-hex-to-ascii-in-java/
	 */
	public String convertHexToString(String hex){

		StringBuilder sb = new StringBuilder();
		StringBuilder temp = new StringBuilder();

		//49204c6f7665204a617661 split into two characters 49, 20, 4c...
		for( int i=0; i<hex.length()-1; i+=2 ){

			//grab the hex in pairs
			String output = hex.substring(i, (i + 2));
			//convert hex to decimal
			int decimal = Integer.parseInt(output, 16);
			//convert the decimal to character
			sb.append((char)decimal);

			temp.append(decimal);
		}
		return sb.toString();
	}
}
